ImpactMojo ImpactMojo
Premium

Econometrics Problem Sets

Applied Causal Inference with Indian Development Data

About These Problem Sets

These problems use realistic scenarios based on major Indian development datasets and policy interventions. Each problem set builds skills progressively, from basic regression to advanced causal inference methods.

Datasets Referenced:

Software: Code provided for both Stata and R. Complete datasets available on ImpactMojo platform.

Problem Set 1: Basic Regression & OLS

BEGINNER

Policy Context: Education and Earnings in Rural India

You're analyzing the relationship between education and income using IHDS household data. The government wants to understand returns to education to justify increased education spending.

Research Question: What is the return to an additional year of schooling in rural India?

Dataset Description (n = 8,432 rural adults)

Variable Description Mean Std Dev
log_income Log monthly income (Rs.) 8.24 0.67
education Years of schooling completed 4.8 4.2
age Age in years 38.2 12.1
female 1 if female, 0 if male 0.41 0.49
sc_st 1 if SC/ST household 0.28 0.45

Part A: Simple Regression (20 minutes)

Step 1: Basic Model

Estimate: log_income = β₀ + β₁education + ε

Interpret β₁ coefficient and significance.

Step 2: Add Controls

Add age, age², female, sc_st to the regression.

How does the education coefficient change?

Step 3: Goodness of Fit

Compare R² between models.

What percentage of income variation is explained?

Step 4: Hypothesis Testing

Test: H₀: β₁ = 0.08 vs H₁: β₁ ≠ 0.08

Use t-test with α = 0.05

Stata Code
* Load data use "ihds_education.dta", clear * Basic regression reg log_income education estimates store model1 * Full model reg log_income education age c.age#c.age female sc_st estimates store model2 * Compare models estimates table model1 model2, b se stats(N r2) * Test hypothesis test education = 0.08
R Code
# Load data library(tidyverse) library(broom) data <- read_csv("ihds_education.csv") # Basic regression model1 <- lm(log_income ~ education, data = data) summary(model1) # Full model model2 <- lm(log_income ~ education + age + I(age^2) + female + sc_st, data = data) summary(model2) # Compare models library(stargazer) stargazer(model1, model2, type = "text") # Test hypothesis library(car) linearHypothesis(model2, "education = 0.08")
Expected Results
  • Simple regression: β₁ ≈ 0.093 (9.3% return per year)
  • Full model: β₁ ≈ 0.076 (7.6% return per year)
  • R² increase: From 0.18 to 0.35
  • Hypothesis test: Fail to reject H₀ (p > 0.05)
Economic Interpretation

Policy Implications: Each additional year of education increases income by approximately 7.6%. For a person moving from 8 to 12 years of education (primary to secondary), income would increase by about 31%.

Caution: This is correlation, not causation. Education might be correlated with ability, family background, or motivation.

Problem Set 2: Instrumental Variables

INTERMEDIATE

Policy Context: School Distance and Education Outcomes

You suspect the education-income relationship suffers from ability bias. Students with higher unobserved ability both stay in school longer AND earn more. You'll use distance to nearest secondary school as an instrumental variable.

Research Question: What is the causal effect of education on income?

Additional Variables

Variable Description Mean Std Dev
distance_school Distance to nearest secondary school (km) 3.2 2.8
district_literacy District literacy rate (%) 67.3 12.4

Part A: IV Estimation (30 minutes)

Step 1: First Stage

Regress education on distance_school and controls.

Check instrument relevance (F-stat > 10).

Step 2: Reduced Form

Regress log_income on distance_school and controls.

This shows total effect of instrument.

Step 3: 2SLS Estimation

Estimate IV model using two-stage least squares.

Compare with OLS results.

Step 4: Validity Tests

Test for weak instruments and endogeneity.

Discuss exclusion restriction.

Stata Code
* First stage regression reg education distance_school age c.age#c.age female sc_st district_literacy test distance_school estat firststage * Reduced form reg log_income distance_school age c.age#c.age female sc_st district_literacy * IV estimation ivregress 2sls log_income (education = distance_school) /// age c.age#c.age female sc_st district_literacy, first * Weak instrument test estat firststage * Endogeneity test estat endogenous
R Code
# Load IV package library(AER) # First stage first_stage <- lm(education ~ distance_school + age + I(age^2) + female + sc_st + district_literacy, data = data) summary(first_stage) # F-test for weak instruments first_stage_f <- summary(first_stage)$fstatistic[1] print(paste("First stage F-statistic:", round(first_stage_f, 2))) # Reduced form reduced_form <- lm(log_income ~ distance_school + age + I(age^2) + female + sc_st + district_literacy, data = data) # IV estimation iv_model <- ivreg(log_income ~ education + age + I(age^2) + female + sc_st + district_literacy | distance_school + age + I(age^2) + female + sc_st + district_literacy, data = data) summary(iv_model) # Compare OLS vs IV ols_model <- lm(log_income ~ education + age + I(age^2) + female + sc_st + district_literacy, data = data) stargazer(ols_model, iv_model, type = "text")
Expected Results
  • First stage F-stat: ≈ 23.4 (instrument is relevant)
  • OLS coefficient: 0.076 (7.6% return)
  • IV coefficient: 0.124 (12.4% return)
  • Hausman test: Reject H₀ (education is endogenous)
Economic Interpretation

LATE Interpretation: For students induced to get more education by being closer to school, the return is 12.4% per year—higher than OLS suggests.

Why IV > OLS? Ability bias was negative! High-ability students may choose lower-paying but personally fulfilling careers, while marginal students induced by proximity get higher returns.

Exclusion Restriction Concerns
  • School distance might affect income through job market access
  • Areas with closer schools might have better infrastructure
  • Distance could proxy for urbanization or development level

Problem Set 3: Difference-in-Differences

INTERMEDIATE

Policy Context: MGNREGA Impact on Agricultural Wages

The Mahatma Gandhi National Rural Employment Guarantee Act (MGNREGA) was rolled out in phases: Phase 1 (200 districts in 2006), Phase 2 (130 districts in 2007), Phase 3 (remaining districts in 2008). You'll evaluate its impact on agricultural wages.

Research Question: Did MGNREGA increase agricultural wages in rural areas?

Dataset: District-Level Panel (2004-2010)

Variable Description Mean Std Dev
log_wage Log daily agricultural wage (Rs.) 4.32 0.28
mgnrega 1 if MGNREGA implemented 0.43 0.49
phase1 1 if Phase 1 district 0.33 0.47
rainfall Annual rainfall (mm) 1123 456

Part A: Basic DiD Analysis (25 minutes)

Step 1: Simple DiD

Compare Phase 1 vs Phase 3 districts, 2005 vs 2007.

Calculate the 2×2 DiD manually.

Step 2: Regression DiD

Estimate: wage = α + βmgnrega + δᵢ + γₜ + ε

Include district and year fixed effects.

Step 3: Event Study

Estimate dynamic effects: 2 years before to 2 years after.

Test for pre-trends.

Step 4: Robustness

Add time-varying controls (rainfall).

Check sensitivity to sample period.

Stata Code
* Prepare data use "mgnrega_wages.dta", clear xtset district_code year * Manual 2x2 DiD (Phase 1 vs Phase 3, 2005 vs 2007) preserve keep if (phase1 == 1 | phase1 == 0) & (year == 2005 | year == 2007) table phase1 year, contents(mean log_wage) restore * Basic DiD regression reghdfe log_wage mgnrega, absorb(district_code year) cluster(district_code) * Add controls reghdfe log_wage mgnrega rainfall, absorb(district_code year) cluster(district_code) * Event study gen years_since_mgnrega = year - first_year_mgnrega forval i = -2/2 { gen mgnrega_`i' = (years_since_mgnrega == `i') } reghdfe log_wage mgnrega_-2 mgnrega_-1 mgnrega_0 mgnrega_1 mgnrega_2, /// absorb(district_code year) cluster(district_code) * Test for pre-trends test mgnrega_-2 = 0 test mgnrega_-1 = 0
R Code
# Load packages library(fixest) library(did) # Basic DiD did_basic <- feols(log_wage ~ mgnrega | district_code + year, data = data, cluster = "district_code") summary(did_basic) # Add controls did_controls <- feols(log_wage ~ mgnrega + rainfall | district_code + year, data = data, cluster = "district_code") # Event study # Create relative time variable data <- data %>% group_by(district_code) %>% mutate(first_treat = min(ifelse(mgnrega == 1, year, NA), na.rm = TRUE), rel_time = year - first_treat) %>% ungroup() # Event study regression event_study <- feols(log_wage ~ i(rel_time, ref = -1) | district_code + year, data = data, cluster = "district_code") iplot(event_study) # Test pre-trends wald(event_study, "rel_time::-2" = 0)
Expected Results
  • DiD coefficient: ≈ 0.087 (8.7% wage increase)
  • Standard error: ≈ 0.023 (significant at 1% level)
  • Pre-trends test: p > 0.10 (no pre-existing trends)
  • Effect size: Rs. 6-8 increase in daily wages
Policy Interpretation

Economic Mechanism: MGNREGA provides outside option for agricultural laborers, forcing farmers to pay higher wages to retain workers.

Magnitude: 8.7% wage increase translates to approximately Rs. 6-8 more per day for agricultural workers—meaningful for poor households.

DiD Best Practices
  • Always plot raw data to visualize trends
  • Test for pre-trends using event study
  • Consider spillover effects to control areas
  • Check robustness to different control groups

Problem Set 4: Regression Discontinuity

ADVANCED

Policy Context: Merit Scholarship and Educational Outcomes

State government provides scholarships to students scoring ≥75% in Class 10 board exams. You'll evaluate whether receiving a scholarship increases the probability of completing Class 12.

Research Question: Does merit-based financial aid increase educational persistence?

Dataset: Class 10 Students (n = 12,847)

Variable Description Mean Std Dev
completed_12 1 if completed Class 12 0.73 0.44
class10_score Class 10 percentage (30-95) 68.2 12.8
scholarship 1 if received scholarship 0.31 0.46
family_income Annual family income (Rs. '000) 89.4 67.2

Part A: Sharp RDD Analysis (35 minutes)

Step 1: Visual Analysis

Plot completion rates vs Class 10 scores.

Look for discontinuity at 75% cutoff.

Step 2: Manipulation Test

Test for bunching of scores at cutoff.

Use McCrary density test.

Step 3: RDD Estimation

Estimate local linear regression around cutoff.

Use optimal bandwidth selection.

Step 4: Validity Tests

Check balance of covariates at cutoff.

Test robustness to bandwidth choice.

Stata Code
* Load RDD packages ssc install rdrobust ssc install rddensity * Visual inspection twoway (scatter completed_12 class10_score, msize(tiny) mcolor(gray)) /// (lfit completed_12 class10_score if class10_score < 75) /// (lfit completed_12 class10_score if class10_score >= 75), /// xline(75, lcolor(red)) legend(off) /// title("Scholarship Effect on Class 12 Completion") * Manipulation test rddensity class10_score, c(75) * Basic RDD estimation rdrobust completed_12 class10_score, c(75) * With covariates rdrobust completed_12 class10_score, c(75) covs(family_income female rural) * Covariate balance test rdrobust family_income class10_score, c(75) rdrobust female class10_score, c(75) * Sensitivity analysis rdrobust completed_12 class10_score, c(75) h(5) rdrobust completed_12 class10_score, c(75) h(10)
R Code
# Load RDD packages library(rdrobust) library(rddensity) # Visual inspection ggplot(data, aes(x = class10_score, y = completed_12)) + geom_point(alpha = 0.3, size = 0.5) + geom_smooth(aes(group = class10_score >= 75), method = "lm", se = TRUE) + geom_vline(xintercept = 75, color = "red", linetype = "dashed") + labs(title = "RDD: Scholarship Effect on Class 12 Completion", x = "Class 10 Score", y = "Completed Class 12") + theme_minimal() # Manipulation test rdd_density <- rddensity(data$class10_score, c = 75) summary(rdd_density) # Basic RDD estimation rdd_basic <- rdrobust(data$completed_12, data$class10_score, c = 75) summary(rdd_basic) # With covariates rdd_covs <- rdrobust(data$completed_12, data$class10_score, c = 75, covs = cbind(data$family_income, data$female, data$rural)) summary(rdd_covs) # Covariate balance balance_income <- rdrobust(data$family_income, data$class10_score, c = 75) balance_female <- rdrobust(data$female, data$class10_score, c = 75) # Bandwidth sensitivity rdd_h5 <- rdrobust(data$completed_12, data$class10_score, c = 75, h = 5) rdd_h10 <- rdrobust(data$completed_12, data$class10_score, c = 75, h = 10)
Expected Results
  • RDD coefficient: ≈ 0.082 (8.2 percentage point increase)
  • 95% CI: [0.031, 0.133]
  • Manipulation test: p = 0.43 (no evidence of sorting)
  • Covariate balance: All p > 0.10
Policy Interpretation

Local Treatment Effect: Students just above the 75% cutoff are 8.2 percentage points more likely to complete Class 12.

External Validity: Effect applies to marginal students around the cutoff—may not generalize to much lower or higher performers.

Cost-Effectiveness: For every 12 scholarships given, approximately 1 additional student completes Class 12.

RDD Limitations
  • Only identifies effects for students near the cutoff
  • May miss longer-term effects (college enrollment, career outcomes)
  • Assumes no other discontinuous programs at 75%

Problem Set 5: Panel Data & Fixed Effects

INTERMEDIATE

Policy Context: Training Programs and Worker Productivity

You have panel data on manufacturing workers before and after participating in skill development programs. Some workers receive training in different years, creating variation for analysis.

Research Question: Do worker training programs increase productivity?

Dataset: Worker Panel (2015-2019, n = 4,832 workers)

Variable Description Mean Std Dev
log_output Log daily output (units) 4.18 0.42
trained 1 if received training by year t 0.23 0.42
experience Years of work experience 8.3 4.7
firm_sales Firm annual sales (Rs. crores) 47.2 23.8

Part A: Fixed Effects Analysis (25 minutes)

Step 1: Pooled OLS

Estimate simple pooled regression.

Ignore panel structure initially.

Step 2: Worker Fixed Effects

Add individual worker fixed effects.

Compare with pooled results.

Step 3: Two-Way Fixed Effects

Add both worker and year fixed effects.

Control for macro trends.

Step 4: Dynamic Effects

Estimate effects 1-3 years after training.

Do benefits persist or fade?

Stata Code
* Set up panel data xtset worker_id year * Pooled OLS reg log_output trained experience firm_sales * Worker fixed effects xtreg log_output trained experience firm_sales, fe * Two-way fixed effects reghdfe log_output trained experience firm_sales, absorb(worker_id year) * Test for fixed effects xtreg log_output trained experience firm_sales, fe xttest3 * Dynamic effects gen trained_lag1 = L.trained gen trained_lag2 = L2.trained gen trained_lag3 = L3.trained reghdfe log_output trained trained_lag1 trained_lag2 trained_lag3 /// experience firm_sales, absorb(worker_id year) * Hausman test (RE vs FE) xtreg log_output trained experience firm_sales, re estimates store re_model xtreg log_output trained experience firm_sales, fe estimates store fe_model hausman fe_model re_model
R Code
# Load panel data packages library(plm) library(fixest) # Convert to panel data panel_data <- pdata.frame(data, index = c("worker_id", "year")) # Pooled OLS pooled <- plm(log_output ~ trained + experience + firm_sales, data = panel_data, model = "pooling") # Worker fixed effects fe_model <- plm(log_output ~ trained + experience + firm_sales, data = panel_data, model = "within") # Two-way fixed effects twoway_fe <- feols(log_output ~ trained + experience + firm_sales | worker_id + year, data = data) # Compare models stargazer(pooled, fe_model, twoway_fe, type = "text") # Hausman test re_model <- plm(log_output ~ trained + experience + firm_sales, data = panel_data, model = "random") phtest(fe_model, re_model) # Dynamic effects data <- data %>% group_by(worker_id) %>% arrange(year) %>% mutate(trained_lag1 = lag(trained, 1), trained_lag2 = lag(trained, 2), trained_lag3 = lag(trained, 3)) %>% ungroup() dynamic_fe <- feols(log_output ~ trained + trained_lag1 + trained_lag2 + trained_lag3 + experience + firm_sales | worker_id + year, data = data)
Expected Results
  • Pooled OLS: β ≈ 0.156 (15.6% increase)
  • Fixed effects: β ≈ 0.087 (8.7% increase)
  • Hausman test: p < 0.01 (prefer fixed effects)
  • Dynamic effects: Persistent for 2-3 years
Economic Interpretation

Selection Bias: Fixed effects estimate (8.7%) much lower than pooled OLS (15.6%), suggesting more motivated workers were selected for training.

Within Estimator: Uses variation in training status within the same worker over time—eliminates time-invariant ability bias.

Policy Impact: Training increases worker output by 8.7% on average, with effects persisting for 2-3 years.

Additional Practice Resources